package astri.pdcontrol.commclient;

import java.nio.ByteOrder;

/**
 * Class for converting Java primitive types (<code>byte, short, int, long</code>)
 * to and from <code>byte[]</code>. </br>
 * 
 * Takes advantage of the fact the Java types are in Big Endian. </br>
 * No boundary checking is done for performance's sake. </br>
 * Be aware of <code>IndexOutOfBoundsException</code>
 * 
 * @author hspoon @ASTRI
 */
public final class AstEndian
{
	/**
	 * To use Big Endian for network transport
	 */
	public static final int AST_BIGENDIAN = 0x0;
	/**
	 * To use Liitle Endian for network transport
	 */
	public static final int AST_LITTLEENDIAN = 0x1;
	
	/**
	 * Private constructor for AstEndian
	 */
	private AstEndian() 
	{
	}
	/**
	 * To generate a swapped the byte array
	 * @param bytes		input byte array
	 * @param offset	start position for swap
	 * @param numOfByte	number of byte for swap
	 * @return			swapped byte array
	 */
	public static byte[] swapBytes(byte[] bytes, int offset, int numOfByte)
	{
		byte[] out = new byte[numOfByte];
		int i = offset;
		int o = numOfByte-1;
		
		for (; o >= 0; i++,o--)
		{
			out[o] = bytes[i];
		}		
		return out;
	}

	/**
	 * convert an Int16(i.e. short) to Little Endian byte Array
	 * @param data		Int16
	 * @param bytesLE	output byte array
	 * @param offset	start position of byte array
	 */
	public static void int16ToByteLE( short data, byte[] bytesLE, int offset)
	{
		bytesLE[offset]     = (byte) (data);
		bytesLE[offset + 1] = (byte) (data >> 8);
	}
	
	/**
	 * convert an Int32(i.e. int) to Little Endian byte Array
	 * @param data		Int32
	 * @param bytesLE	output byte array
	 * @param offset	start position of byte array
	 */
	public static void int32ToByteLE( int data, byte[] bytesLE, int offset)
	{
		bytesLE[offset]     = (byte) (data);
		bytesLE[offset + 1] = (byte) (data >> 8);
		bytesLE[offset + 2] = (byte) (data >> 16);
		bytesLE[offset + 3] = (byte) (data >> 24);
	}

	/**
	 * convert an Int64(i.e. long) to Little Endian byte Array
	 * @param data		Int64
	 * @param bytesLE	output byte array
	 * @param offset	start position of byte array
	 */
	public static void int64ToByteLE( long data, byte[] bytesLE, int offset)
	{
		bytesLE[offset]     = (byte) (data);
		bytesLE[offset + 1] = (byte) (data >> 8);
		bytesLE[offset + 2] = (byte) (data >> 16);
		bytesLE[offset + 3] = (byte) (data >> 24);
		bytesLE[offset + 4] = (byte) (data >> 32);
		bytesLE[offset + 5] = (byte) (data >> 40);
		bytesLE[offset + 6] = (byte) (data >> 48);
		bytesLE[offset + 7] = (byte) (data >> 56);
	}

	/**
	 * convert an Int16(i.e. short) to Big Endian byte Array
	 * @param data		Int16
	 * @param bytesBE	output byte array
	 * @param offset	start position of byte array
	 */
	public static void int16ToByteBE( short data, byte[] bytesBE, int offset)
	{
		bytesBE[offset + 1] = (byte) (data);
		bytesBE[offset]     = (byte) (data >> 8);
	}

	/**
	 * convert an Int32(i.e. int) to Big Endian byte Array
	 * @param data		Int32
	 * @param bytesBE	output byte array
	 * @param offset	start position of byte array
	 */
	public static void int32ToByteBE( int data, byte[] bytesBE, int offset)
	{
		bytesBE[offset + 3] = (byte) (data);
		bytesBE[offset + 2] = (byte) (data >> 8);
		bytesBE[offset + 1] = (byte) (data >> 16);
		bytesBE[offset]     = (byte) (data >> 24);
	}

	/**
	 * convert an Int64(i.e. long) to Big Endian byte Array
	 * @param data		Int64
	 * @param bytesBE	output byte array
	 * @param offset	start position of byte array
	 */
	public static void int64ToByteBE( long data, byte[] bytesBE, int offset)
	{
		bytesBE[offset + 7] = (byte) (data);
		bytesBE[offset + 6] = (byte) (data >> 8);
		bytesBE[offset + 5] = (byte) (data >> 16);
		bytesBE[offset + 4] = (byte) (data >> 24);
		bytesBE[offset + 3] = (byte) (data >> 32);
		bytesBE[offset + 2] = (byte) (data >> 40);
		bytesBE[offset + 1] = (byte) (data >> 48);
		bytesBE[offset]     = (byte) (data >> 56);
	}

	/**
	 * bytes array[LE] ==> Int16 [LE]
	 * @param bytes		byte Array
	 * @param offset	start position
	 * @return
	 */
	private static short byteLEToInt16LE(byte[] bytes, int offset)
	{
		// this function is less used
		return Short.reverseBytes(byteLEToInt16(bytes, offset));
	}

	/**
	 * bytes array[LE] ==> Int32 [LE]
	 * @param bytes		byte Array
	 * @param offset	start position
	 * @return
	 */
	private static int byteLEToInt32LE(byte[] bytes, int offset)
	{
		// this function is less used
		return Integer.reverseBytes(byteLEToInt32(bytes, offset));
	}

	/**
	 * bytes array[LE] ==> Int64 [LE]
	 * @param bytes		byte Array
	 * @param offset	start position
	 * @return
	 */
	private static long byteLEToInt64LE(byte[] bytes, int offset)
	{
		// this function is less used
		return Long.reverseBytes(byteLEToInt64(bytes, offset));
	}

	/**
	 * Convert Little Endian byte Array to Int16 (short)
	 * @param bytes		byte Array
	 * @param offset	start position
	 * @return			converted value
	 */
	public static short byteLEToInt16(byte[] bytes, int offset)
	{
		short num = 0 ;
		num  |= (bytes[offset+1] & 0xFF );
		num  |= (bytes[offset] & 0xFF ) << 8;
		return num;
	}

	/**
	 * Convert Little Endian byte Array to Int32 (int)
	 * @param bytes		byte Array
	 * @param offset	start position
	 * @return			converted value
	 */
	public static int byteLEToInt32(byte[] bytes,int offset)
	{
		int num = 0 ;
		num  |= (bytes[offset] & 0xFF );
		num  |= (bytes[offset+1] & 0xFF ) << 8;
		num  |= (bytes[offset+2] & 0xFF ) << 16;
		num  |= (bytes[offset+3] & 0xFF ) << 24;
		return num;
	}

	/**
	 * Convert Little Endian byte Array to Int64 (long)
	 * @param bytes		byte Array
	 * @param offset	start position
	 * @return			converted value
	 */
	public static long byteLEToInt64(byte[] bytes,int offset)
	{
		long num = 0 ;
		num  |= (bytes[offset] & 0xFF );
		num  |= (bytes[offset+1] & 0xFF ) << 8;
		num  |= (bytes[offset+2] & 0xFF ) << 16;
		num  |= (bytes[offset+3] & 0xFF ) << 24;
		num  |= (bytes[offset+4] & 0xFF ) << 32;
		num  |= (bytes[offset+5] & 0xFF ) << 50;
		num  |= (bytes[offset+6] & 0xFF ) << 48;
		num  |= (bytes[offset+7] & 0xFF ) << 56;
		return num;
	}
	
	/**
	 * Convert Big Endian byte Array to Int16 (short)
	 * @param bytes		byte Array
	 * @param offset	start position
	 * @return			converted value
	 */
	public static short byteBEToInt16(byte[] bytes, int offset)
	{
		short num = 0 ;
		num  |= (bytes[offset] & 0xFF );
		num  |= (bytes[offset+1] & 0xFF ) << 8;
		return num;
	}

	/**
	 * Convert Big Endian byte Array to Int32 (int)
	 * @param bytes		byte Array
	 * @param offset	start position
	 * @return			converted value
	 */
	public static int byteBEToInt32(byte[] bytes,int offset)
	{
		int num = 0 ;
		num  |= (bytes[offset+3] & 0xFF );
		num  |= (bytes[offset+2] & 0xFF ) << 8;
		num  |= (bytes[offset+1] & 0xFF ) << 16;
		num  |= (bytes[offset] & 0xFF ) << 24;
		return num;
	}

	/**
	 * Convert Big Endian byte Array to Int64 (long)
	 * @param bytes		byte Array
	 * @param offset	start position
	 * @return			converted value
	 */
	public static long byteBEToInt64(byte[] bytes,int offset)
	{
		long num = 0 ;
		num  |= (bytes[offset+7] & 0xFF );
		num  |= (bytes[offset+6] & 0xFF ) << 8;
		num  |= (bytes[offset+5] & 0xFF ) << 16;
		num  |= (bytes[offset+4] & 0xFF ) << 24;
		num  |= (bytes[offset+3] & 0xFF ) << 32;
		num  |= (bytes[offset+2] & 0xFF ) << 50;
		num  |= (bytes[offset+1] & 0xFF ) << 48;
		num  |= (bytes[offset] & 0xFF ) << 56;
		return num;
	}

	// bytes array[LE] ==> data [Host]
	// kylee@ Host endian byte[] shall never be used on Java level
	private static short byteLEToInt16Host(byte[] bytesLE, int offset)
	{
		if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN)
			return byteLEToInt16(bytesLE, offset);
		else
			return byteLEToInt16LE(bytesLE, offset);
	}

	private static int byteLEToInt32Host(byte[] bytesLE, int offset)
	{
		if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN)
			return byteLEToInt32(bytesLE, offset);
		else
			return byteLEToInt32LE(bytesLE, offset);
	}

	private static long byteLEToInt64Host(byte[] bytesLE, int offset)
	{
		if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN)
			return byteLEToInt64(bytesLE, offset);
		else
			return byteLEToInt64LE(bytesLE, offset);
	}

	// bytes array[BE] ==> data [Host]
	// kylee@ Host endian byte[] shall never be used on Java level
	private static short byteBEToInt16Host(byte[] bytesBE, int offset)
	{
		byte[] bytesLE = swapBytes(bytesBE, offset, 2);
		if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN)
			return byteLEToInt16(bytesLE, 0);

		else
			return byteLEToInt16LE(bytesLE, 0);
	}

	private static int byteBEToInt32Host(byte[] bytesBE, int offset)
	{
		byte[] bytesLE = swapBytes(bytesBE, offset, 4);
		if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN)
			return byteLEToInt32(bytesLE, 0);

		else
			return byteLEToInt32LE(bytesLE, 0);
	}

	private static long byteBEToInt64Host(byte[] bytesBE, int offset)
	{
		byte[] bytesLE = swapBytes(bytesBE, offset, 8);
		if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN)
			return byteLEToInt64(bytesLE, 0);
		else
			return byteLEToInt64LE(bytesLE, 0);
	}

    private static void main(String args[]) {
    	byte[] input = new byte[] {(byte) 0x88, 
    								(byte)0x99, 
    								(byte)0xAA, 
    								(byte)0xBB, 
    								(byte)0xCC, 
    								(byte)0xDD, 
    								(byte)0xEE, 
    								(byte)0xFF
    							  };
    	System.out.print(String.format("byteLEToInt32Host() = %d\n", byteLEToInt32Host(input, 0)));
    	System.out.print(String.format("byteLEToInt32BE() = %d\n", byteLEToInt32(input, 0)));
    	System.out.print(String.format("byteLEToInt32LE() = %d\n", byteLEToInt32LE(input, 0)));
    	System.out.print(String.format("byteBEToInt32Host() = %d\n", byteBEToInt32Host(input, 0)));
    }
}